package unit;

import ui.Main;
import ui.MainMap;
import util.expression.ConstantTerm;
import util.expression.Expression;
import util.expression.Function;
import util.expression.GeneralTerm;
import util.expression.SineFunction;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

/**
 * 맵 클래스
 */
public class Field
{
	private Expression map;
	private Polygon mapLine;
	private ArrayList<Unit> units = new ArrayList<Unit>();
	private int score = 0;
	private Component component;

	/**
	 * 생성자
	 * @param component
	 */
	public Field(Component component)
	{
		this.component = component;

		map = new Function(new GeneralTerm(0.5, 1, new SineFunction(new GeneralTerm(1, 1))),
				new ConstantTerm(-2.0));
		mapLine = new Polygon();

		for (int i = 0; i < 5120; i++)
			mapLine.addPoint(i, (int) (-map.getValue(i * 0.005) * 100 + 100));
	}

	/**
	 * 유닛들의 좌표를 갱신하는 메서드
	 */
	public void adjustAllUnits()
	{
		for (int i = 0; i < units.size(); i++)
			units.get(i).adjust();
	}

	/**
	 * 모든 유닛을 그리는 메서드
	 * @param g : 그리기 객체
	 */
	private void drawAllUnits(Graphics g)
	{
		for (Unit i : units)
			i.draw(g);
	}

	/**
	 * 맵을 출력하는 메서드
	 * @param g : 그리기 객체
	 */
	private void drawMap(Graphics g)
	{
		g.setColor(Color.BLACK);
		int[] xpoints = mapLine.xpoints;
		int[] ypoints = mapLine.ypoints;
		int npoints = mapLine.npoints;

		g.drawPolyline(xpoints, ypoints, npoints);

		g.setColor(new Color(185, 122, 87));
		for (int i = 0; i < npoints; i++)
			g.drawLine(xpoints[i], ypoints[i] + 1, xpoints[i], 1000);
	}

	/**
	 * 그리기 메서드 종합
	 * @param graphics : 그리기 객체
	 */
	public void draw(Graphics graphics)
	{
		drawMap(graphics);
		drawAllUnits(graphics);
	}

	/**
	 * 유닛을 추가하는 메서드
	 * @param unit : 추가할 유닛
	 */
	public void addUnit(Unit unit)
	{
		units.add(unit);
	}

	/**
	 * 유닛 클래스
	 */
	public abstract class Unit
	{
		protected double x;      // x좌표
		protected double y;      // y좌표
		protected double deltaX; // x변화량
		protected double deltaY; // y변화량

		abstract void adjust();

		public abstract void draw(Graphics g);
	}

	/**
	 * 탱크(캐릭터) 클래스
	 */
	public class Tank extends Unit
	{
		private Image body = Toolkit.getDefaultToolkit().getImage("./res/body.png");
		private Image barrel = Toolkit.getDefaultToolkit().getImage("./res/barrel.png");

		private int x;
		private int y;
		private BufferedImage bufferedTank;
		private AffineTransform at;

		private int mouseX;
		private int mouseY;

		public Tank(Component component)
		{
			this(component, 0, 0);
		}

		public Tank(Component component, int x, int y)
		{
			this.x = x;
			this.y = y;

			bufferedTank = new BufferedImage(112, 40, BufferedImage.TYPE_INT_ARGB);
			Graphics2D g = bufferedTank.createGraphics();
			g.drawImage(body, 33, 0, null);
			g.drawImage(barrel, 0, 8, null);

			//		g.drawImage(bufferedTank, x, y, null);

			// create the transform, note that the transformations happen
			// in reversed order (so check them backwards)
			at = new AffineTransform();

			// 4. translate it to the center of the component
			int xSize = bufferedTank.getWidth() / 2 + 15;
			int ySize = bufferedTank.getHeight();
			at.translate(x + xSize, y + ySize);
			at.scale(-1, 1);

			// 3. do the actual rotation
			at.rotate(Math.toRadians(13.0));

			at.translate(-xSize, -ySize);

			// 마우스 리스너를 등록한다.
			component.addMouseListener(new MouseAdapter()
			{
				/**
				 * 마우스를 눌렀을 때 호출되는 메서드
				 * @param e : 마우스상태 객체
				 */
				@Override
				public void mousePressed(MouseEvent e)
				{
					addUnit(new Bullet(Tank.this.x + 70, Tank.this.y + 13,
							(e.getX() - Tank.this.x - 70) * 2,
							(Tank.this.y + 13 - e.getY()) * 2, 6));
				}
			});
			component.addMouseMotionListener(new MouseMotionAdapter()
			{
				/**
				 * 마우스가 움직였을 때 호출되는 메서드
				 * @param e : 마우스상태 객체
				 */
				@Override
				public void mouseMoved(MouseEvent e)
				{
					// 쏘려는 각도를 구한다.
					mouseX = e.getX();
					mouseY = e.getY();
				}
			});
		}

		@Override
		void adjust()
		{
		}

		/**
		 * 탱크를 출력하는 메서드
		 * @param g
		 */
		@Override
		public void draw(Graphics g)
		{
			Graphics2D g2d = (Graphics2D) bufferedTank.getGraphics();
			g2d.drawImage(body, 33, 0, null);
			g2d.drawImage(barrel, 0, 8, null);

			// 발사 궤적을 그린다.
			g.setColor(Color.BLACK);
			g.drawLine(x + 70, y + 13, mouseX, mouseY);

			// 탱크를 그린다.
			((Graphics2D) g).drawImage(bufferedTank, at, null);

			// 디버그 모드면 포탄의 궤적도 출력한다.
			if (Main.debugMode)
			{
				Polygon trajectory = new Polygon();

				double deltaX = (mouseX - Tank.this.x - 70) * 2;
				double deltaY = (Tank.this.y + 13 - mouseY) * 2;

				double nextX = Tank.this.x + 70;
				double nextY = Tank.this.y + 13;

				for (int i = 0; i < 1000; i++)
				{
					trajectory.addPoint((int) nextX, (int) nextY);
					nextX += deltaX / 10;
					nextY -= deltaY / 10;
				}

				g.drawPolyline(trajectory.xpoints, trajectory.ypoints, trajectory.npoints);
			}
		}
	}

	/**
	 * 탄환 클래스
	 */
	public class Bullet extends Unit
	{
		private double thickness; // 포탄의 두께 (픽셀단위)

		/**
		 * X, Y 좌표를 전달받는 생성자
		 * @param x         : x좌표
		 * @param y         : y좌표
		 * @param deltaX    : x변화량
		 * @param deltaY    : y변화량
		 * @param thickness : 포탄의 두께 (픽셀단위)
		 */
		public Bullet(double x, double y, double deltaX, double deltaY, double thickness)
		{
			this.x = x;
			this.y = y;
			this.deltaX = deltaX;
			this.deltaY = deltaY;
			this.thickness = thickness;

			// 포탄쏘는 소리와 탄피떨어지는 소리를 낸다.
//		    new Sound("fire.wav").start();
//		    new Thread()
//		    {
//			    @Override
//			    public void run()
//			    {
//				    try
//				    {
//					    sleep(650);
//					    new Sound("shell.wav").start();
//				    }
//				    catch (InterruptedException e)
//				    {
//					    e.printStackTrace();
//				    }
//			    }
//		    }.start();
		}

		/**
		 * 이번 프레임의 포탄위치를 계산하고 갱신하는 메서드
		 */
		@Override
		public void adjust()
		{
			try
			{
				double nextX = x + deltaX / 10;
				double nextY = y - deltaY / 10;

				double inclination = (nextY - y) / (nextX - x);

				Expression expression =
						new Function(new GeneralTerm(inclination, 1),
								new ConstantTerm(y),
								new ConstantTerm(inclination * -x));

				for (Unit i : units)
				{
					if (i instanceof Target)
					{
						Target target = (Target) i;
						double resultY = expression.getValue(target.x);
						if (target.y - 5 <= resultY && resultY <= target.y + 5)
						{
							units.remove(target);
							score += 30;
							break;
						}
					}
				}

				if (mapLine.ypoints.length >= (int) x && y >= mapLine.ypoints[(int) x])
				{
//					System.out.println("다았다!");
					// 도착지점과 닫았으면
//			    if (800 - 8 <= Math.abs(x) && Math.abs(x) <= 800 + 8 && deltaY <= -1)
//			    {
//				    System.out.print("축하합니다. 승리하셨습니다!");
//				    System.exit(0);
//			    }

					y = mapLine.ypoints[(int) x] - thickness / 2;
					deltaY *= (-0.3);
					if (Math.abs(deltaY) < 1)
						deltaY = 0;

					// 마찰력 구현
					deltaX = Math.max(0, deltaX - 1);
				}

				x += deltaX / 10;
				y -= deltaY / 10;

				deltaY = deltaY - 1;
			}
			catch (Exception e)
			{
			}
		}

		/**
		 * 바뀐 좌표에 맞게 포탄을 다시 출력하는 메서드
		 * @param g : 그리기 객체
		 */
		public void draw(Graphics g)
		{
			// 포탄을 출력한다.
			g.drawOval(
					((int) (x - thickness / 2)),
					((int) (y - thickness / 2)),
					((int) thickness),
					((int) thickness));

			g.drawString("점수 : " + score, 200 + MainMap.getScrollPos(), 50);
		}
	}

	/**
	 * 목표 클래스
	 */
	public class Target extends Unit
	{
		private int x, y;

		public Target()
		{
			x = (int) (Math.random() * 4096) + 300;
			y = (int) (Math.random() * (618 - mapLine.ypoints[x]));
		}

		void adjust()
		{
		}

		@Override
		public void draw(Graphics g)
		{
			g.drawOval(x - 5, y - 5, 10, 10);
		}
	}
}